主页链接索引  
 

Using ISO C++ As A Better C
Ichiroh Kanaya, Wakayama University, Japan


PREAMBLE

Too busy to learn C++?

Basically, using C++ compiler as a better C is not a good idea: you should master full feature of C++ language especially when you are developing a large software.  However, you are too busy to learn another language.  Okay, I'll show you a subset of ISO C++ feature that has still sufficient power to boost up your programming cycle.

Which C++ compiler?

Just use ISO C++.  The ISO C++ is much improved from classical C++ flavors (including ARM 2nd, the ANSI C++ base document.)  For example, Gnu's g++ 2.95 is a good C++ compiler: it supports almost full feature of ISO C++ language and the standard library.

USING ISO C++

Use C++ style comment

The most popular feature added to C  is the new comment style.

Example of C++ style comment
// This is C++ style comment
/* This is C style comment */
#if 0
  Comment... (not recommended)
#endif

Namespace

The namespace is a newly added keyword of ISO C++.  It provides user controllable name space that means the prefix of function name is no longer needed when you write some library.
 

Example of namespace
namespace mine {
  void foo() { /* ... */ }  // foo() is equivalant to foo(void) in C++
}
void bar()
{
  mine::foo();              // Okay
  foo();                    // Error (unless the global foo() exists)
  using namespace mine;     // Hereafter you can omit 'mine::'
  foo();                    // Okay, calls mine::foo()
}

Don't use constant macro, use constant value

The const value in C++ is truely constant.  It is always a better replacement for #define constant definition.  (Putting const value definitions in header files is never stupid idea in C++.)  For example, write

const int buff_size = 100;

instead of using macro as follows:

#define BUFF_SIZE 100

The const value has its type so that the compiler can check ill usage of constant value; while the macro does not have type, because the macro is preprocessed before compiling.  

Further description

The const keyword also means static.  This is why you can simply replace #define with const int.  If you really want global constant, use

extern const int a = 10;

semantics for definition and use

extern const int a;

for declaration.

Don't use function macro, use inline function, or use function template

The following function (called inline function)

inline float sqr(float x) { return x * x; }

is a modern replacement of the following macro.

#define SQR(X) ((X) * (X))

Some of you may claim that the macro version is still better because it is not type-checked.  For example,

#define MAX(X, Y) (((X) > (Y)) ? (X) : (Y))

is commonly used macro as a generic (untyped) function.  To obtain such generic function without macro, C++ provides template.  See the following example.
 

Example of template
template <typename T> T max(T x, T y) { return (x > y) ? x : y; }
void foo()
{
  int a = 10, b = 20;
  int c = max(a, b);         // Okay: int max(int x, int y)
  float x = 10.0, b = 20.0;
  float z = max(x, y);       // Okay: float max(float x, float y)
}

You can put automatic value definitions almost everywhere

In C++, you can put definitions of automatic value almost everywher.  For example,

for (int i = 0; i < 100; ++i) /* ... */

is commonly used technique to localize loop-counter into the loop itself.

Use string, don't use char-array

The ISO C++ standard library provides string type.  

Example of string
#include <string>                 // Not <string.h> (ANSI C header)
using namespace std;
void foo()
{
  string s = "abc";               // assignment
  string t;
  t = s;                          // copy
  string u = t + s;               // append
  if (s > u) /* never happens */  // compare
}

The standard library of ISO C++ covers all features appeared in ANSI C standard library.  See ISO C++ reference book (e.g. Bjarne Stroustrup: The C++ Programming Language Third Edition) for more detail.

Use iostream instead of stdio

Forget all about <stdio.h>.  The use of printf is considerably dangerous because the printf nomaly lacks argument type checking.  

Example of <iostream> (1)
#include <iostream>   // Not <iostream.h>, just <iostream>
using namespace std;  // 'cout' is declared in the 'std' name space
int main()            // You cannot omit 'int' in ISO C++
{
  // C++ style
    cout << 123 << 15.0 << 'a' << "\nabc" << endl;
                      // 'endl' prints '\n' and then flush the buffer.
  // C style
    fprintf(stdout, "%d%f%c%s\n", 123, 15.0, 'a', "\nabc"), fflush(stdout);
  // In C++, you can omit the final 'return 0' in the main function.
}

The stdout corresponds to cout and, as you guess, the stdin corresponds to cin, the stderr corresponds to cerr.  Here's an example of cin.  

Example of <iostream> (2)
#include <iostream>
#include <string>
using namespace std;
int main()
{
  int i;
  char c;
  float f;
  // C++ style
    string s;
    cin >> i >> c >> f >> s;
  // C style
    const int buffer_size = 100;
    char buffer[buffer_size];
    char ca[buffer_size];
    fgets(buffer, buffer_size, stdin);  // Using fscanf is crazy idea
    sscanf(buffer, "%i %c %f %s", &i, &c, &f, ca);
}

Do not use array, try "vector"

There is no array type in C.  There are only pointer and continuos memory allocation method.  In ISO C++, the standard library provides a true array, named vector.  

Example of vector
#include <vector>
using namespace std;
void foo()
{
  vector<int> vi(100);  // 'vi[100]' is error
  vi[99] = 99;          // Okay
  vi[100] = 100;        // Okay: the vector is expandable
  int s = vi.size();    // Number of elements
  vector<float> vf;     // Another example
  vf[20] = 20.0;        // Okay
  vf.resize(100);       // Reallocating (replacement of 'realloc')
}

Though the vector itself only provides 1D array, you can define the vector of vector by recursive use of vector as follows.

vector<vector<int> > two_dimensional_array;

You can also define something like matrix type by yourself, but definition of user defined type (or class) requires some knowledge of C++.

Try "map"

See this example.  I'm not joking.  The example really works.  

Example of map
#include <iostream>
#include <string>
#include <map>
using namespace std;
int main()
{
  // read and write
    map<string, float> m;
    m["abc"] = 10.0;
    m["def"] = m["abc"];
  // 'foreach'
    typedef map<string, float>::const_iterator CI;
    for (CI p = m.begin(); p != m.end(); ++p)
      cout << p->first << '\t' << p->second << '\n';
}

Forget "malloc", use "new" operaotr

The C++ prohibits conversion from void* to any other pointer type.  That means, the malloc is completely obsolete.  The C++ gives you a new operator to allocate memory.

int *pi = new int;

Don't use free for freeing memory allocated by new operator.  Use delete instead.

delete pi;

If you really want to allocate array,

float *pf = new float[100];

And free it in this way.

delete[] pf;

Anyway, you should consider vector before trying array style memory allocation.

Further description
If malloc fails to allocate memory, it returns 0.  If new fails to allocate memory, it throws an exception as the default behavior.  You can trap (or catch) that exception at arbitrary level of nests of function calling, or you can even modify the default behavior by giving the new handler to the standard library's call-back mechanism.

Use "static cast"

If you can, avoid casting.  The implicit casting rule is clearly documented in C++, it is much better than Pascal.  If you really want to cast something to other type, use ISO C++ style casting to give a chance to the compiler to check the casting safe or not.

float f = 100.0;
int a = static_cast<int>(f);

Use exception, instead of returning zero, or providing call-back mechanism

You can throw an exception if something goes wrong in your function.  And later, someone (including you) can catch the exception.
 

Example of exception
const int error_in_foo = 100;
void foo()
{
  // ...
  if (something_goes_wrong) throw error_in_foo;
  // ...
}
void bar()
{
  try {
    // ...
    foo();
    // ...
  }
  catch (int i) {
    switch (i) {
      case error_in_foo:
        // Something goes wrong in foo()
        break;
      // ...
    }
  }
}

 

Further description
Throwing int is, actually, not good idea in C++ programming.  After you learn class concept of c++, you should throw user-defined type, not built-in type like int.

Use "reference" and "const reference"

With the C++, you can define arguments of functions as call-by-reference.  For example,

void Double(int &i) { i *= 2; }  // C++ style

is really similar to

void _Double(int *i) { *i *= 2; }  // C style

The difference appears only at calling this function.

int i = 1;
Double(i);
_Double(&i);

Reference operaotr & is no longer necessary unless you use C style function.  The first code is as exactly same as following Pascal procedure.

procedure X2(var i: integer);
begin
  i := i * 2;
end;

The const reference is worth using to pass non-small object to a function.

void print_string(const string &s) { cout << s << endl; }

Boolean

You can use real boolean (bool) in ISO C++, that holds true or false.  The bool type can be implicitly converted to int with value of 1 (true) or 0 (false).

@#$%&!?, use "and", "or", "not" keywords

This is a good news if, only if, you love Pascal.  Next table shows newly added keywords to C++.  

New keywords
and &&
or ||
not !

CONCLUSION

I recommend true ISO C++ as your primary language for Unix programming.  Due to its object oriented programming feature, ISO C++ is a very natural way to construct a relatively large software.

Last update: March 22, 2000